#include "alias.hpp"
#include "lexer.hpp"

#include <fstream>
#include <ctime>
#include <cmath>
#include <map>

Variable bi_alias(vector<Variable>& argv, Environment& env)
{
	Variable ret;
	int argc = (int)argv.size();
	if( argc != 4 ) {
		throw_error(ERROR_ARGUMENTCOUNT);
	}
	ret = argv[1];
	if(ret.type != tVarname || ret.str == "core") {
		throw(ERROR_ARGUMENTS);
	}
	if(argv[3].type != tInline) {
		throw(ERROR_INLINECODE);
	}
	
	argv[3].str = trim_le_whitespace(argv[3].str);
	// Get the arguments
	Variable &args = argv[2];
	switch(args.type) {
		case tNumeric:
			// Argument count
			args = args.i;
			args.type = tAliasWithArgc;
			break;
		case tInline: {
			// Argument list
			vector<Variable> tmpargs = tokenize(args.str,env, DISABLESCOPE);
			for(vector<Variable>::iterator it = tmpargs.begin(); it != tmpargs.end(); it++) {
				if(it->type == tVarname) {
					args.arr.push_back(*it);
				}
			}
			args.type = tAliasWithArgList;
			args.i = args.arr.size();
			break;
		}
		case tArray:
			// Type list
			for(unsigned i=0;i<args.arr.size();i++) {
				if(args.arr[i].type != tTypeID) {
					args.arr[i].i = args.arr[i].type;
					args.arr[i].str = vartypeToStr(args.arr[i].type);
					args.arr[i].type = tTypeID;
				}
			}
			args.type = tAliasWithTypeList;
			args.i = args.arr.size();
			break;
		default:
			throw ERROR_ARGUMENTS;
	}
	args.str = argv[3].str;
	
	// Get a reference to the alias with that name
	vector<Variable> &aref = env._alias[ret.str];
	bool wasset = false;
	// Find out if the alias is already declared
	for(unsigned i=0;i<aref.size();i++) {
		if(aref[i].type == args.type && args.i == aref[i].i) {
			if(args.type == tAliasWithArgc) {
				aref[i] = args;
				wasset = true;
			} else if(args.type == tAliasWithTypeList) {
				bool isDifferent = false;
				for(unsigned j=0;j<args.arr.size();j++) {
					if(args.arr[j].i != aref[i].arr[j].i) {
						// There's a difference in the type list
						isDifferent = true;
					}
				}
				if(!isDifferent) {
					// It's identical, argument-wise, to the current one
					// So just overwrite it
					aref[i] = args;
					wasset = true;
				}
			} else if(args.type == tAliasWithArgList) {
				aref[i] = args;
				wasset = true;
			}
		}
	}
	if(!wasset) {
		// We didn't overwrite anything
		aref.push_back(args);
	}
	return ret;
}

Variable bi_aget(vector<Variable>& argv, Environment& env)
{
	Variable ret;
	int argc = (int)argv.size();
	if( argc == 2 ) {
		ret.makearr(0);
		if(env._alias.find(argv[1].str) != env._alias.end() ) {
			vector<Variable> &total = env._alias[argv[1].str];
			for(vector<Variable>::iterator it = total.begin(); it != total.end(); it++) {
				ret.arr.push_back(*it);
			}
		} else {
			ret = Variable();
		}
	} else if( argc == 1 ) {
		Variable tmp;
		ret.makearr(0);
		for(map <string, vector<Variable> >::iterator i=env._alias.begin();i!=env._alias.end();i++) {
			vector<Variable> &total = env._alias[i->first];
			for(vector<Variable>::iterator it = total.begin(); it != total.end(); it++) {
				stringstream ss;
				ss << i->first << " : " << it->i << "\n\t" << it->str;
				tmp = ss.str();
				tmp.i = it->i;
				ret.arr.push_back(tmp);
			}
		}
	} else {
		throw_error(ERROR_ARGUMENTCOUNT);
	}
	return ret;
}

Variable bi_call(vector<Variable>& argv, Environment& env)
{
	Variable ret;
	int argc = (int)argv.size();
	
	if( env._alias.find(argv[1].str) == env._alias.end() ) {
		throw_error(ERROR_ALIAS);
	}
	
	// Find out the "best" alias to match
	vector<Variable> &tmp_aliases = env._alias[argv[1].str];
	string full = "";
	vector<Variable>::iterator it, it2;
	bool rightType=false;
	for(it=tmp_aliases.begin();it != tmp_aliases.end(); it++) {
		if(it->i == -1) {
			// This alias matches the "any args" rule
			full = it->str;
			break;
		} else if(it->i == (argc - 2)) {
			// It has the right number of args
			if(it->type == tAliasWithArgc || it->type == tAliasWithArgList) {
				// This alias is correct
				full = it->str;
				it2 = it;
				rightType = true;
			} else if(it->type == tAliasWithTypeList) {
				// Compare the types
				try {
					for(unsigned i=0;i<it->i;i++) {
						Variable tmp = argv[i+2];
						if(it->arr[i].type != tVarname) {
							env.verify(tmp);
						}
						if(tmp.type != it->arr[i].i) {
							// It's not a match
							 throw 0;
						}
					}
				} catch(int e) {
					continue;
				}
				full = it->str;
			}
		}
	}
	if(rightType) {
		it = it2; // Make it point back to the CORRECT location
	}
	if(full == "") {
		// The specific alias wasn't found
		throw ERROR_ALIAS;
	}
	
	map<string, vector<Variable> > trepl = env.repl; // Backups of the replacements
	
	vector<Variable> bi_argv; // __argv__
	vector<Variable> bi_tmp_argv; // Individual args
	bi_tmp_argv.push_back(Variable());
	long flags = env.flags; // Environment flags
	
	env.inscope();
	string tmpstr;
	if(it->type != tAliasWithArgList) {
		for(int i = 0;(i+1)<argc;i++) {
			ostringstream ss;
			ss << "_" << i << "_";
			tmpstr = ss.str();
			bi_tmp_argv[0] = argv[i+1];
			env.repl[tmpstr] = bi_tmp_argv;
			if(i!=0) {
				bi_argv.push_back(argv[i+1]);
			}
		}
		bi_tmp_argv[0] = argc-2;
		env.repl["__argc__"] = bi_tmp_argv;
		env.repl["__argv__"] = bi_argv;
	} else {
		for(unsigned i = 0;i<it->arr.size();i++) {
			// Get the next allowed name in the list
			env.getvar(it->arr[i].str) = argv[i+2];
		}
	}
	
	bi_tmp_argv[0] = argv[1];
	env.repl["_0_"] = bi_tmp_argv;
	
	try {
		orate(full,env,DISABLESCOPE);
	} catch(ErrorType e) {}
	env.flags = flags;
	env.outscope();
	
	env.repl = trepl;

	ret=env.getvar("__return__");
	return ret;
}
